/**
 *
 * @file sniffer.c
 *
 * @brief This file contains the network sniffer
 *
 * $Id: sniffer.c 121 2009-01-15 08:13:29Z reverdy.camille $
 */

/*
 * This file is part of Tsar.
 *
 * Tsar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tsar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tsar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include <sniffer.h>



sniffer_t * start_sniffer( opt_t * o )
{
    sniffer_t * snifferHandle;

    snifferHandle = ( sniffer_t * )Malloc( sizeof( sniffer_t ) );

    /* Some initialization should avoid unpredictable behaviors */
    snifferHandle->receiver = NULL;            /* Pcap handle */
    snifferHandle->dumper   = NULL;            /* Pcap dumper */
    snifferHandle->dumpFilename = o->pcapFile; /* Dump filename */
    
    if ( open_sniff_session( o, snifferHandle ) )
    {
        print_error( "Cannot open a packets capture session!\n" );
        free( snifferHandle );
        snifferHandle = NULL;
    }

    return snifferHandle;
}

int open_sniff_session( opt_t * o, sniffer_t * session )
{
    int retVal;
    char errbuf[PCAP_ERRBUF_SIZE] = "";

    /* Open network interface */
    if ( ( session->receiver = pcap_open_live( o->iface, CAPTURE_SNAPLEN, o->promisc, o->timeoutMs, errbuf ) ) == NULL )
    {
        print_error( "Tsar sniffer : %s\n", errbuf );
        retVal = 1;
    }
    /* Check for data link type : Ethernet required */
    else if ( pcap_datalink( session->receiver ) != DLT_EN10MB )
    {
        print_error( "Supplied network interface (%s) is not an Ethernet one\n", o->iface );
        pcap_close( session->receiver );
        retVal = 2;
    }
    else if ( apply_filter( session->receiver, o ) )
    {

        retVal = 3;
    }
    /* open a pcap dumper to store the captured traffic into a file */
    else if ( ( session->dumper = pcap_dump_open( session->receiver, session->dumpFilename ) ) == NULL )
    {
        print_error( "Setting network traffic dumper : %s\n", pcap_geterr( session->receiver ) );
        pcap_close( session->receiver );
        retVal = 4;
    }
    else
    {
        /* Success */
        pcap_setnonblock( session->receiver, true, errbuf );
        retVal = 0;
    }

    return retVal;
}

void shutdown_sniffer( sniffer_t * session )
{
    if ( session != NULL )
    {
        print( MESSAGE_INFO, "Closing session\n" );
    
        /* Close the dump file if any */
        debug( "\t[+] %s : Pcap dumper is %p\n", __FUNCTION__, session->dumper );
        if ( session->dumper != NULL )
            pcap_dump_close( session->dumper );

        debug( "\t[+] %s : Pcap session is %p\n", __FUNCTION__, session->receiver );
        /* Close the pcap handle if any */
        if ( session->receiver != NULL )
            pcap_close( session->receiver );
    
        free( session );
    }
}

int apply_filter( pcap_t * pcap, opt_t * o )
{
    int retVal = 0;
    struct bpf_program filter;

    /* no filter supplied */
    if ( o->snifferFilter == NULL )
        return 0;

    debug( "Applying filter  \"%s\"\n", o->snifferFilter );

    if ( pcap_compile( pcap, &filter, o->snifferFilter, true, 0 ) == -1 )
    {
        print_error( "[PARSER] %s\n", pcap_geterr( pcap ) );
        retVal = 1;
    }
    /* Apply the previously compiled BPF to the pcap handle */
    else if ( pcap_setfilter( pcap, &filter ) == -1 )
    {
        print_error( "[PARSER] %s\n", pcap_geterr( pcap ) );
        pcap_freecode( &filter );
        retVal = 2;
    }
    else
    {
        pcap_freecode( &filter );
        print( MESSAGE_INFO, "Filter %s applyied on the built-in sniffer\n" );
    }

    return retVal;
}

/* Called as a callback on activity on the pcap socket */
void sniffer_dump( int sock, void * datas )
{
    tsar_t * session;

    session = ( tsar_t * )datas;
    pcap_dispatch(
            /* Our pcap receiver from which traffic is captured */
            session->sniffer->receiver,
            /* Number of packets to capture at a time */
            1,
            /* callback : what to do with captured packet (save it to our dump file)*/
            &pcap_dump,
            /* dump session to transmit to the callback */
            ( unsigned char * )session->sniffer->dumper );
    /* flush the updated dump */
    pcap_dump_flush( session->sniffer->dumper );
}

